package cn.com.nes.common.util;

import cn.com.nes.site.entity.Variable;
import cn.hutool.core.util.NumberUtil;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 处理公式工具类
 *
 * @author gaos
 * @date 2019/12/01
 */
public class MathUtils {

    private static final String division = "/";

    private static final String minus = "-";

    private static final String plus = "+";

    private static final String multiplication = "*";

    private static final String power = "^";

    /**
     * 短横线
     */
    private static final String STRIGULA = "-";

    private static final String DEFAULT_DIGITS = "0";

    private static final String FIRST_DEFAULT_DIGITS = "1";


    /**
     * 生产一个随机的指定位数的字符串数字
     * @param length
     * @return
     */
    public static String randomDigitNumber(int length) {
        //1000+8999=9999
        int start = Integer.parseInt(makeUpNewData("", length));
        //9000
        int end = Integer.parseInt(makeUpNewData("", length + 1)) - start;
        return (int) (Math.random() * end) + start + "";
    }

    /**
     * @param target 目标数字
     * @param length 需要补充到的位数, 补充默认数字[0], 第一位默认补充[1]
     */
    public static String makeUpNewData(String target, int length) {
        return makeUpNewData(target, length, DEFAULT_DIGITS);
    }

    /**
     * @param target 目标数字
     * @param length 需要补充到的位数
     * @param add    需要补充的数字, 补充默认数字[0], 第一位默认补充[1]
     */
    public static String makeUpNewData(String target, int length, String add) {
        if (target.startsWith(STRIGULA)) {
            target.replace(STRIGULA, "");
        }
        if (target.length() >= length) {
            return target.substring(0, length);
        }
        StringBuffer sb = new StringBuffer(FIRST_DEFAULT_DIGITS);
        for (int i = 0; i < length - (1 + target.length()); i++) {
            sb.append(add);
        }
        return sb.append(target).toString();
    }

    /**
     * 运算符号的优先级, $为结束符
     */
    private static final HashMap<String, Integer> priority = new HashMap<String, Integer>(5) {{
        put("$", -1);
        put("(", 0);
        put("+", 1);
        put("-", 1);
        put("*", 2);
        put("/", 2);
        put("^", 3);
    }};

    /**
     * 从公式中获取采集字段名, 采集字段由{}包裹
     *
     * @param formula 公式
     * @return
     */
    public static List<String> getFieldsFromFormula(String formula) {
        List<String> fields = new ArrayList<>();
        String regexp = "(?<=\\{)(.+?)(?=})";
        Pattern pattern = Pattern.compile(regexp);
        Matcher matcher = pattern.matcher(formula);
        while (matcher.find()) {
            String field = matcher.group();
            fields.add(field);
        }
        return fields;
    }

    /**
     * 计算公式
     *
     * @return
     */
    public static Object calFormula(String formula, List<Object> values) {
        int size = MathUtils.getFieldsFromFormula(formula).size();
        if (size != (values.size())) {
            return null;
        }
        try {
            String regexp = "(?<=})(.)(?=\\{)";
            Pattern pattern = Pattern.compile(regexp);
            Matcher matcher = pattern.matcher(formula);
            int i = 0;
            BigDecimal res = new BigDecimal(values.get(0).toString());
            while (matcher.find()) {
                String signs = matcher.group();
                BigDecimal val = new BigDecimal(values.get(++i).toString());
                if (division.equals(signs)) {
                    res = res.divide(val);
                } else if (minus.equals(signs)) {
                    res = res.subtract(val);
                }
            }
            return res;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 格式化公式, 并校验公式的正确性
     *
     * @param formula 公式
     * @return 格式化结果
     */
    public static String formatFormula(String formula) {
        formula = formula.replace(" ", "");
        //将大括号和中括号统一转化为小括号
        String[] marks = formula.split("");
        Stack<String> stack = new Stack<>();
        StringBuilder formatterFormula = new StringBuilder();
        StringBuilder lastNumber = new StringBuilder();
        for (int i = 0; i < marks.length; i++) {
            String mark = marks[i];
            //遇到数字和小数点, 暂时存入lastNumber,遇到+-号,判断是正负号就存入lastNumber
            if (mark.matches("[0-9.a-zA-Z]")) {
                lastNumber.append(mark);
            }
            if (mark.matches("[+\\-*/^)]")) {
                //判断正负号
                if (mark.matches("[+\\-]")) {
                    //在公式第一位的加减号是正负号
                    if (i == 0) {
                        lastNumber.append(mark);
                        continue;
                    }
                    //在符号后面的加减号是正负号
                    else if (formatterFormula.length() > 0) {
                        if (lastNumber.length() == 0) {
                            String lastString = formatterFormula.substring(formatterFormula.length() - 2, formatterFormula.length() - 1);
                            if (lastString.matches("[+\\-*/^(]")) {
                                lastNumber.append(mark);
                                continue;
                            }
                        }
                    }
                }
                //校验待输出数字是否正确
                String number = lastNumber.toString();
                if (!number.matches("^([+\\-]?\\d+)(\\.\\d+)?$|[+\\-]?[a-zA-Z]")) {
                    return "非法";
                }
                formatterFormula.append(number).append(" ").append(mark).append(" ");
                if (mark.equals(")")) {
                    if (i < marks.length - 1) {
                        formatterFormula.append(marks[++i]).append(" ");
                    }
                }
                lastNumber = new StringBuilder();
            }
            //TODO 完善括号的校验
            if (mark.matches("[(]")) {
                formatterFormula.append(mark).append(" ");
            }
        }
        formatterFormula.append(lastNumber);
        return formatterFormula.toString();
    }

    /**
     * 将中缀表达式转换为后缀表达式
     *
     * @param exp 中缀表达式
     * @return 后缀表达式
     */
    public static String convertToPostfix(String exp) {
        StringBuilder postfix = new StringBuilder();
        //初始化一个空栈, 用以存放出栈的符号
        Stack<String> operatorStack = new Stack<>();
        operatorStack.push("$");
        for (String mark : exp.split(" ")) {
            //数字直接存入后缀表达式中
            if (mark.matches("^([+\\-]?\\d+)(\\.\\d+)?$")) {
                postfix.append(mark).append(" ");
            }
            //左括号直接入栈
            else if ("(".equals(mark)) {
                operatorStack.push(mark);
            }
            //右括号则开始持续出栈并写入后缀表达式中, 直到一个左括号出栈后停止, 所有括号不写入后缀表达式
            else if (")".equals(mark)) {
                while (true) {
                    String top = operatorStack.pop();
                    if ("(".equals(top)) {
                        break;
                    }
                    postfix.append(top).append(" ");
                }
            }
            //如果是运算符号, 判断该运算符和栈顶运算符的优先级
            else if (mark.matches("[+\\-*/^]")) {
                //栈顶运算符
                String top = operatorStack.lastElement();
                /*
                当前运算符额优先级大于栈顶运算符额优先级, 入栈
                当前运算符的优先级小于等于栈顶运算符的优先级, 则出栈
                 */
                while (priority.get(mark) <= priority.get(top)) {
                    postfix.append(operatorStack.pop()).append(" ");
                    top = operatorStack.lastElement();
                }
                operatorStack.push(mark);
            }
        }
        while (!operatorStack.empty()) {
            String top = operatorStack.pop();
            //结束符$
            if (!"$".equals(top)) {
                postfix.append(top).append(" ");
            }
        }
        return postfix.toString();
    }

    /**
     * 计算后缀表达式
     *
     * @param postfix 后缀表达式
     * @return 计算结果
     */
    public static BigDecimal calPostfix(String postfix) {
        Stack<String> stack = new Stack<>();
        for (String mark : postfix.split(" ")) {
            if (!mark.matches("[+\\-*/^]")) {
                stack.push(mark);
            } else {
                BigDecimal num2 = new BigDecimal(stack.pop());
                BigDecimal num1 = new BigDecimal(stack.pop());
                BigDecimal res = new BigDecimal(0);
                if (plus.equals(mark)) {
                    res = num1.add(num2);
                } else if (minus.equals(mark)) {
                    res = num1.subtract(num2);
                } else if (multiplication.equals(mark)) {
                    res = num1.multiply(num2);
                } else if (division.equals(mark)) {
                    res = num1.divide(num2, 4, BigDecimal.ROUND_HALF_UP);
                } else if (power.equals(mark)) {
                    res = new BigDecimal(String.valueOf(Math.pow(num1.doubleValue(), num2.doubleValue())));
                }
                stack.push(res.toString());
            }
        }
        return new BigDecimal(stack.pop());
    }

    public static BigDecimal formulaCalculator(String formula, List<Variable> variables) {
        for (Variable variable :
                variables) {
            formula = formula.replace(variable.getVariable(), variable.getValue());
        }
        return calPostfix(convertToPostfix(formula));
    }

    /**
     * 计算系统公式
     *
     * @param formula   公式
     * @param variables 变量
     * @return 计算结果
     */
//    public static BigDecimal calSysFormula(String formula, String variables, Map<String, List<OtherData>> otherDataMap) {
//        try {
//            //将变量转换为json数组
//            List<FormulaVariable> formulaVariables = JSONArray.parseArray(variables, FormulaVariable.class);
//            for (Map.Entry<String, List<OtherData>> entry : otherDataMap.entrySet()) {
//                String sbId = entry.getKey();
//                List<OtherData> otherDataList = entry.getValue();
//                Map<String, String> variableMap = new HashMap<>(formulaVariables.size());
//                formulaVariables.forEach(variable -> {
//                    if (sbId.equals(variable.getSbId())) {
//                        variableMap.put(variable.getField(), variable.getName());
//                    }
//                });
//                for (OtherData otherData : otherDataList) {
//                    String field = otherData.getF();
//                    String value = otherData.getS();
//                    if (variableMap.containsKey(field)) {
//                        String variableName = variableMap.get(field);
//                        if (StrUtil.isNotEmpty(variableName)) {
//                            formula = formula.replace(variableName, value);
//                        }
//                    }
//                }
//            }
//            return calPostfix(convertToPostfix(formula));
//        } catch (Exception e) {
//            return null;
//        }
//    }

    public static String eval(String expression) {
        String result = "";
        try {
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("js");
            result = engine.eval(expression).toString();

            if(NumberUtil.isNumber(result)){
                return new BigDecimal(result).setScale(3, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
            }else{
                return "";
            }
//            return NumberUtil.isNumber(result) ? result : "";
        } catch (ScriptException e) {
            return "";
        }
    }

    public static void main(String[] args) {
        String eval = MathUtils.eval("40.9+22.7");
        System.out.println(eval);
    }
}
